// vim:sw=4:ai:aw

// STL includes
#include <iostream>	// cout, cerr, endl
using std::cout;
using std::cerr;
using std::endl;

// Systeem includes
#include <setjmp.h>	// setjmp/longjmp
#include <signal.h>	// signal()
#include <getopt.h>	// getopt(...)
#include <stdlib.h>
// Voor meer informatie, zie: man 3 getopt

// En onze eigen includes
#include "asserts.h"	// require/ensure/check/notreached
// ===================================================================

// De diverse geheugenbeheer algoritmes:
#include "RandomFit.h"	// de RandomFit allocator
#include "BuddySystem.h" // de BuddySystem allocat0r
#include "WorstFit.h" // de WorstFit allocator
#include "BestFit.h"

// Deze array van pointers is de koppeling tussen
// main en de (methodes van de) diverse algoritmes.
Allocator * allocator[] = {
new RandomFit(), // koppeling naar de RandomFit allocator
#define	RF_ALLOC	0	// index in deze tabel voor RandomFit
		// voeg hier je eigen modules toe
new BuddySystem(),		// new BuddySystem(),		// vervang xxx door de goede naam
#define	BS_ALLOC	1	// vervang XX door een handige code
new BestFit(),
#define	BF_ALLOC	2
new WorstFit(),
#define	WF_ALLOC	3

		// enz, enz
		};

// ===================================================================

// globale, hulp functie
inline // NB deze 'inline' is alleen wat optimalisatie
bool vraagkans() {
	int r = (rand()%10)+1;
	return r < 7;
	// De "vraag" kans is 10 op 16, maar we kijken naar een paar
	// bitjes "halverwege" de gegeven r en niet naar de "laatste".
	// >> = schuif bitjes naar rechts
	// %  = modulo = rest na deling
}

// ======================================================

#include "Main.h"	// De pseudo applicatie

Main::Main(Allocator * beheerder, int size) :
	beheerder(beheerder), size(size) {
	// nooit iets geloven ...
	require( beheerder != 0 );
	require( size > 0 );
}

// actie: na afloop alles netjes opruimen.
Main::~Main() {
	// zijn er nog objecten over?
	cout << endl << "Alle overige objecten opruimen." << endl;
	while (!objecten.empty()) {
		vergeetOudste();
	}
}

// actie: vraag om geheugen (onze versie van 'new')
void Main::vraagGeheugen(int omvang) {
	cout << "Vraag " << omvang << "\t";

	// Deze controles overslaan als we aan het testen zijn.
	if (!tflag)
		require( (0 < omvang) && (omvang <= size) ); // is 'omvang' wel geldig ?

	const Area * ap = beheerder->alloc(omvang);

	if(ap == 0){
		cout << endl << "Niet genoeg geheugen om " << omvang << " op te vragen." << endl;
	}else{

		cout << "kreeg gebied " << ap->getBase() << "..." << ap->getEnd() << ":"
				<< ap->getSize() << endl;

		// En nou moeten we controlen of er geen overlap
		// bestaat met wat we al eerder hadden gekregen ...
		ALIterator i;
		for (i = objecten.begin(); i != objecten.end(); i++) {
			const Area * xp = *i; // m.a.w.  objecten[ i ]

			if (((xp->getBase() <= ap->getBase())
					&& (ap->getBase() <= xp->getEnd())) // ap begint  IN xp !
					|| ((xp->getBase() <= ap->getEnd()) && (ap->getEnd()
							<= xp->getEnd())) // ap eindigt IN xp !
			) {
				// Dit zou eigenlijk een "fatal error" moeten zijn,
				// maar bij de RandomFit zal dit wel vaker gebeuren
				// dus voorlopig alleen maar melden dat het fout is ...
				cout << "Oeps, nieuwe gebied overlapt met " << xp->getBase()
						<< "..." << xp->getEnd() << ":" << xp->getSize() << endl;
				break; // verder zoeken is niet meer zo zinvol ...
				// ... levert alleen maar meer uitvoer op.
			}
		}
		// ... bedenk zelf maar of er misschien nog andere dingen zijn om te controleren

		// Het gekregen gebied moeten we natuurlijk wel onthouden
		objecten.push_back(const_cast<Area*> (ap));
		// NB De 'push_back' schijnt niet te houden van 'const'
	}
}

// actie: geef een gekregen gebied weer terug (onze versie van 'delete')
void Main::vergeetOudste() {
	require( ! objecten.empty() ); // hebben we eigenlijk wel wat ?
	const Area * ap = objecten.front(); // het oudste gebied opzoeken, ...
	cout << "Retour " << ap->getBase() << "..." << ap->getEnd() << ":"
			<< ap->getSize() << endl;
	objecten.pop_front(); // ... uit de lijst halen
	beheerder->free(ap); // ... en vrij geven
}

// =========== ALGORITME METEN =============

// zomaar wat onzin acties
// bedenk zelf wat zinvollers
void Main::randomscenario(int aantal) {
	// eerst even de random generator wat opschudden
#ifndef	unix		// voor devcpp onder windows,
	srand( time( 0 ) ); // windows heeft geen getpid'
#else
	srand(time(0) ^ getpid());
#endif

	// doe tig-keer "iets"
	for (int x = 0; x < aantal; x++) {
		int r = (rand()%50)+11; // gooi de dobbelsteen
		if (objecten.empty() // we hebben nog niets of ...
				|| vraagkans()) // ... we kiezen voor geheugen vragen
		{
			// vraag om ruimte, maar niet meer dan wat er zou kunnen zijn
			r = (rand()%50)+11; // dobbelsteen
			r %= (size - 1); // maximaal n-1
			vraagGeheugen(r + 1); // minstens 1 unit
		} else // vrijgeven mits ...
		if (!objecten.empty()) // ... we iets hebben
		{
			// geef het oudste gebied weer terug
			vergeetOudste();
		}
		// else dan doen we een keer niets
	}
}

// =========== ALGORITME TESTEN ==========

// Elke test die hieronder uitgevoerd wordt
// zou een 'assert' failure moeten veroorzaken
// die de normale executie volgorde afbreekt.
// Die "exception" wordt op zijn beurt afgevangen door 'onAbort'
// waarna we weer terug komen in deze functie (dankzij setjmp/longjmp)
// en de volgende test kunnen doen.
// Eigenlijk is dit wat er "onder tafel" gebeurt bij "try ... catch".


jmp_buf jmpbuf; // buffer voor "executie bookmark" (setjmp/longjmp)
// NB Dit moet een globale variabele zijn
// omdat 'onAbort' er ook bij moet kunnen.


void Main::testing() {
	int fase = 0; // Houdt bij hoe ver we zijn in de test procedure.

	tflag = true; // Leg 'vraagGeheugen' even het zwijgen op.

	setjmp( jmpbuf ); // Maak een "executie bookmark" voor "hier"
	// HINT: Denk aan "try/catch" in Java en C++.
	// Na elke 'test' komen we weer hier terug
	// voor de volgende test.

	cerr << "Stap " << (fase++) << ":\n";

	switch (fase) {
	case 1:
		cerr << "Om niets vragen ...\n";
		vraagGeheugen(0); // dit mag niet kunnen ...
		// ... dus hier zouden we niet mogen komen ...
		cerr << "FAILED\n"; // ... maar de controle heeft gefaald
		/*FALLTHRU*/
	case 2:
		cerr << "Te veel vragen ...\n";
		vraagGeheugen(size + 1);
		cerr << "FAILED\n";
		/*FALLTHRU*/
	case 3:
		cerr << "Te weinig vragen ...\n";
		vraagGeheugen(-1);
		cerr << "FAILED\n";
		/*FALLTHRU*/
	case 4:
		cerr << "Om alles vragen ...\n";
		vraagGeheugen(size); // PAS OP: dit mag dus wel!
		cerr << "OKE\n";
		/*FALLTHRU*/
	case 5:
		// voeg zonodig nog andere testcases toe
	default:
		cerr << "Einde code testen\n";
		tflag = false; // voor het geval dat,
		// dit voorkomt een oneindige lus.
		break;
	}
}

// Functie voor software interrupt afhandeling.
void onAbort(int signo) {
	// Een z.g. "forward declaration"
	extern bool tflag; // De echte declaratie staat verderop

	cerr << "Caught signal " << signo << "...";

	// Zijn we aan het testen ?  ...
	if (tflag && (signo == SIGABRT)) {
		// .. dan was dit "te verwachten".
		cerr << "GOOD.\n";
		longjmp(jmpbuf, 1); // ga terug naar de "executie bookmark"
		// HINT: Denk aan "throw" in Java.
	} else
		// Niet? dan is er echt iets fout gegaan
		cerr << "Eh?\n";

#ifndef	unix		// voor devcpp onder windows,
	system("PAUSE"); // anders is je venster meteen weg.
#endif
	exit(1);
}

// ===================================================================
// Het hoofdprogramma.
// Handel opties af, kies beheerder, enz
// en dan gaan we wat spelen.

// Globale hulpvariabelen voor main en doOptions
Allocator * beheerder = 0; // de gekozen Allocator
int size = 1024; // de omvang van het nep geheugen
int aantal = 1000; // hoe vaak doen we het ...
bool tflag; // true als we de code willen "testen"
// anders wordt er "gemeten".


// Analyseert de meegegeven opties.
// Kan/zal bovenstaande globale variabelen veranderen !
void doOptions(int argc, char * argv[]) {
	char options[] = "s:a:tr23b"; // de opties die we willen herkennen
	// s: voor: -s xxx = omvang nepgeheugen
	// a: voor: -a xxx = aantal acties
	// t  voor: -t = code testen (i.p.v. performance meten)
	//
	// Voor de beheeralgoritmes bieden we ...
	// r  voor: -r = RandomFit allocator
	// Als je alle algoritmes zou bouwen dan wordt
	// het zoiets als "s:a:trfnbwpm2"
	// f  voor: -f = first-fit
	// n  voor; -n = next-fit
	//  enz
	// 2  voor: -2 = buddy
	// Voor meer informatie: zie man 3 getopt

	int opt = 0; // "huidige" optie karakter
	do {

		opt = getopt(argc, argv, options); // de uitpluizer

		switch (opt) // welke optie hebben we gezien ...
		{
		case 's': // -s xxx = size
			size = atol(optarg); // atol = "ascii to long"
			break;

		case 'a': // -a xxx = aantal
			aantal = atol(optarg); // atol = "ascii to long"
			break;

		case 't': // -t = code testen
			tflag = true;
			break;

		case 'r': // -r = RandomFit allocator gevraagd
			require( beheerder == 0 ); // eens gekozen, blijft gekozen
			beheerder = allocator[RF_ALLOC];
			break;

			/* voor Buddy bijvoorbeeld */
		 case '2':	// -2 = buddy allocator gevraagd
			 require( beheerder == 0 );
			 beheerder = allocator[BS_ALLOC];
			 break;
		 case '3':	// -3 = WorstFit allocator gevraagd
			 require( beheerder == 0 );
			 beheerder = allocator[WF_ALLOC];
			 break;
		case 'b':	// -b = bestfit allocator gevraagd
			 require( beheerder == 0 );
			 beheerder = allocator[BF_ALLOC];
			 break;
		case -1: // = einde opties
			break;

		default: // eh? iets onbekends gevonden
			// foutmelding geven ...
			cerr << "Usage: " << argv[0] << ", valid options are " << options
					<< "\n";
			// en ermee kappen
#ifndef	unix				// voor DevCPP onder windows,
			system("PAUSE"); // anders is je venster meteen weg.
#endif
			exit(1); // 1 = "something went wrong"
		}
	} while (opt != -1); // tot einde opties
}

// Programma executie begint hier ...
int main(int argc, char * argv[]) {
	// Installeer de ABORT signal handler.
	signal(SIGABRT, onAbort); // Om de "abort" interrupt af te kunnen vangen
	// die misschien gegenereerd wordt door de
	//   require/check/ensure/notreached
	// macro's van asserts.h als dingen niet kloppen.

	// opties afhandelen.
	// neveneffect: kan diverse globale variabelen veranderen.
	doOptions(argc, argv);

	// is er wel een beheerder gekozen ?
	if (beheerder) {
		// even een veiligheidscontrole.
		// hoe groot is het fictieve geheugen ....
		check( size > 0 );

		// oke, omvang beschikbare geheugen instellen
		beheerder->setSize(size);

		cerr << "Using " << beheerder->getType() << " " << aantal
				<< " times on " << size << " bytes\n";

		// oke, gaan we nu even lekker spelen ...
		Main * mp = new Main(beheerder, size);

		if (tflag) // De -t optie gezien ?
			mp->testing(); // ga dan de code testen
		else
			mp->randomscenario(aantal);
		// .. uiteraard moet hierboven straks wat anders konden.

		// en daarna alles weer opruimen
		delete mp;
		delete beheerder;

	} else {
		// foutje!
		cerr << "Oeps, geen beheerder gekozen ....\n";
	}

#ifndef	unix		// voor devcpp onder windows,
	system("PAUSE"); // anders is je venster meteen weg.
#endif

	return 0; // 0=alles is goed gegaan
}
